package com.dollar.juc.threadLocal.simpleDateFormat;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * ThreadLocal 实现的多线程下安全的 SafeSimpleDateFormat（最佳实践）
 * ThreadLocal 其实相当于一个代理对象，
 * 引入一个代理对象，把获取每个线程所持有的Object object对象的细节隐藏掉。
 * 这个JDK的设计更为合理一点，因为ThreadLocal只是一个我们所说的ObjectProxy代理工具类，
 * 内部不应该持有任何与线程有关的属性，所以我们设置的线程本地化对象，
 * 在自己Thread类里所持有也更为容易理解些，也就是说ThreadLocalMap由Thread持有更为合理些。
 * <p>
 * 原来整个获取value的顺序就是：线程Thread->ThreadLocalMap->Entry->value
 * <p>
 * 我们是怎么样从Entry的数组中获取某一个的Entry的呢？
 * 根据传入的这个ThreadLocal的threadLocalHashCode计算坐标值，
 * 然后根据坐标值再从Entry数据里获取对应的Entry对象，从而获取到Entry里的value值
 * <p>
 * 应用：
 * zuul的源码的实现和我们上面的流程图是一样的，先是执行preRoute，再执行route，最后执行postRoute，
 * 那同学们，问题来了，我们在流程图里可以看到，每个route都是有上下文信息层层传递的，
 * 但是对应route方法是没有入参的，他们之间是如何传递的呢？
 * 每个请求的Request和Response封装到一个RequestContext里，那么这个RequestContext里有什么呢？
 * 就是一个 ThreadLocal
 * 对的，没错就是ThreadLocal，这就说明zuul每个请求都是一个单独的线程的来处理，
 * 通过ThreadLocal来实现上下文信息的传递
 * <p>
 * ThreadLocal在Spring源码框架的使用场景，由于我们java代码里加了Transactional注解，
 * 其实会被TranctionInterceptor拦截执行对应的invoke方法，然后执行对应invokeWithInTransaction方法
 * 总的来说就是获取一个数据库连接，然后开始一个事务，封装了一些事务信息等组成了一个TransactionInfo对象
 * 把事务信息组成一个TransactionInfo对象之后，然后把这些信息都保存到了当前线程里的ThreadLocal里，
 * 这样再执行后面事务的提交和回滚的时候，都是从当前线程里获取的事务信息的，
 * 这也说明了spring的事务管理是不能跨线程执行的，否则找不到对应的事务信息啦
 * <p>
 * <p>
 * Tests:
 * 1. 多线程安全 passed
 * 2. 内存安全 TODO
 * <p>
 * ThreadLocal的内存泄漏点
 * 1. tl对象有两个引用：一个是 tl，一个是 ThreadLocal.ThreadLocalMap中的 <Entry.Key>
 * 你将强引用 tl 设为 null，另一个引用被Java设计为弱引用，所以就可以了
 * 2. Entry.Value 指向的内存对象
 * 这需要我们使用完 tl，主动调用 tl.remove()，触发删除对应的 Entry
 * <p>
 * 参考：
 * https://ifeve.com/%E6%89%8B%E6%92%95%E9%9D%A2%E8%AF%95%E9%A2%98threadlocal%EF%BC%81%EF%BC%81%EF%BC%81/
 */
public class SafeSimpleDateFormat {

    private static ThreadLocal<SimpleDateFormat> tl;
    private String pattern;

    public SafeSimpleDateFormat(String p) {
        this.pattern = p;
        tl = ThreadLocal.withInitial(() -> new SimpleDateFormat(this.pattern));
    }

    public Date parse(String source) throws ParseException {
        Date date = null;
        try {
            //if get()==null, initValue() will be called to init tl.
            SimpleDateFormat simpleDateFormat = tl.get();
            date = simpleDateFormat.parse(source);
        } finally {
            /**
             * 业务执行完，记得remove tl，进一步出发删除 Entry，释放 V，防止内存泄漏
             */
            tl.remove();
        }
        return date;
    }
}
